//此文件是封装获取token、过期更新token方法，以及新增/获取/删除临时与永久素材方法
//另外封装了 request插件Promise化异步转同步
const sha1=require('sha1');  //引入sha1加密工具
const fs=require('fs')
// const request=Promise.promisify(require('request'));
const request=require('request')  //get\post请求工具，比内置http简单

const prefix='https://api.weixin.qq.com/cgi-bin/';
let api={
    access_token:prefix+'token?grant_type=client_credential&',
    upload:prefix+'media/upload?',
    permanentPublic:prefix+'material/add_material?',
    permanentImgUrl:prefix+'media/uploadimg?',
    permanentNews:prefix+'material/add_news?',
    permanentMediaId:prefix+'material/get_material?',
    getCount:prefix+'material/get_materialcount?',
    getList:prefix+'material/batchget_material?',
    delMedia:prefix+'material/del_material?'
};
var keep

function Wechat(opts){   //创建一个构造函数，去存接收信息和方法
    var that = this
    this.appID = opts.appID;
    this.appsecret = opts.appsecret;
    this.readAcc=opts.getAccess;   //把opts的get方法和数据给这个构造函数去验证
    this.saveAccess=opts.saveAccess;  //保存有效的access方法
    this.getBody=function(postData){
        return new Promise((resolve, reject)=>{   //创建promise为了将data传出去
            request(postData,function (err,response,body) {
                if(err){
                    reject(err)
                    return
                }
                resolve(body)  //request工具请求时指定了返回JSON所以body已是一个对象
            })
        })
    };
    this.getAcc()
}
Wechat.prototype.getAcc= function() {
    var that=this
    if(keep&&this.isValidAccess(keep)){
        console.log('is new data')
        return Promise.resolve(keep)
    }
    return that.readAcc().then(data=>{  //getAccess方法是从txt文件获取data往下操作，用promise解决回调地狱
        // console.log('获取:'+data);
        try{   //预防异常用try包裹
            data=JSON.parse(data)  //如果接收到正常数据就字符串转json
        }catch(err){
            return that.updataAccess();  //如果异常或没有数据就使用更新方法（更新access官方也有demo
        }
        if (that.isValidAccess(data)){  //isValidAccess方法才是真正开始验证数据有没、是否最新，函数在下面创建
            return Promise.resolve(data)   //如果数据有效则用promise传下去给then里的方法，解决金字塔嵌套
        }else{
            return that.updataAccess()  //否则数据无效过时或没获取到就使用更新access方法
        }
    }).then(async (data)=>{   //接收上面promise传来有效的access，这样promise解决了过多嵌套。then是链式操作fn
        if (!data){
            console.log('have not data');
            return;
        }
        keep=data
        console.log('saveAccess')
        that.saveAccess(data)   //将数据存起来
        return Promise.resolve(data)
    }).catch(err=>{
        throw err
    })
}

Wechat.prototype.getTicket=function(noncestr,timestamp,ctxUrl){
    return this.getAcc().then(async (data) => {
        const url=`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${data.access_token}&type=jsapi`
        return new Promise((resolve, reject) => {
            request({url:url,json:true,method:'GET'},function (err,res,body) {
                if(err)throw('请求jsapi_ticket出错')
                const data=[  //在公众号后台jssdk配置授权域名，一定是“域名！！” 不是http网址
                    "noncestr="+noncestr,"jsapi_ticket="+body.ticket,"timestamp="+timestamp,"url="+ctxUrl
                ]

                let str=data.sort().join("&")
                resolve(sha1(str))
            })
        })
    })
}

Wechat.prototype.isValidAccess=(data)=>{
    if (!data|| !data.access_token|| !data.expires_in){
        return false
    }
    let expires_in=data.expires_in;
    let now=new Date().getTime();
    return now < expires_in;
};
Wechat.prototype.updataAccess=function(){  //这个更新access方法可以参考官方提供
    let appID=this.appID;
    let appsecret=this.appsecret;
    let url=`${api.access_token}appid=${appID}&secret=${appsecret}`;
    let getdata={};
    return new Promise((resolve, reject)=>{   //创建promise为了将data传出去
        request.get(url,function (err,response,body) {
            console.log('in updata');
            let data=body
            getdata=JSON.parse(data);
            getdata.expires_in=new Date().getTime()+(getdata.expires_in-20)*1000;
            resolve(getdata) //抛出给主函数继续用data向下执行then
        });
    })
};
//下面是上传临时素材
Wechat.prototype.updataPublic=function(filePath,msgType){
    let that=this
    let form={
        media:fs.createReadStream(filePath)  //公众号传文件必须提交formdata表单且带media属性（流数据）
    }
    return this.getAcc().then(data=>{
        let access_token=data.access_token  //上面传来已经JSON.parse
        let url=`${api.upload}access_token=${access_token}&type=${msgType}`;
        return that.getBody({url:url,method:'POST',formData:form,json:true})
    })
};

Wechat.prototype.saveTemp=function(mediaId,filePath) {  //获取临时素材
    return this.getAcc().then((data)=>{
        let access_token=data.access_token
        let url=`https://api.weixin.qq.com/cgi-bin/media/get?access_token=${access_token}&media_id=${mediaId}`;
        return new Promise((resolve, reject) => {
            request(url).pipe(fs.createWriteStream(filePath)).once('close', () => {
                console.log('ok 存储成功');
                resolve({code:'001',msg:'存储成功'})

            })
        })
    })
}
//下面是上传永久素材
Wechat.prototype.permanentPublic=function(permanent){
    let that=this
    let form={}
    if(!permanent.newsContent){
        form.media=fs.createReadStream(permanent.filePath)  //公众号必须提交formdata表单且带media属性（流数据）
    }
    return this.getAcc().then(data=>{
        let access_token=data.access_token  //getAcc方法传来已经JSON.parse
        if(permanent.msgType) {
            let url=`${api.permanentPublic}access_token=${access_token}&type=${permanent.msgType}`
            if(permanent.msgType==='video'){
                //注意description一定是字符串组成的对象，不是对象
                // form.description='{"title":"XmlContent.title","introduction":"XmlContent.description"}'
                form.description=permanent.videoDescription  //表单需增加一个description属性
                return that.getBody({url:url, method: 'POST', formData: form, json: true})
            }
            return that.getBody({url:url, method: 'POST', formData: form, json: true})
        }
        else if(permanent.newsContent){
            let body=permanent.newsContent
            let url=`${api.permanentNews}access_token=${access_token}`;
            //注意只要不是POST表单数据，就是POST发送请求体/body作为请求，上面文件是iformData表单，非文件都是请求体body
            return that.getBody({url:url,method:'POST',body:body,json:true}) //工具不同，有些是提交ctx.body
        }else{
            let url=`${api.permanentImgUrl}access_token=${access_token}`;
            return that.getBody({url:url,method:'POST',formData:form,json:true})
        }
    })
};
//下面是用永久madie_id换取内容，JSON格式
Wechat.prototype.permanentMediaId=function(mediaId){
    let that=this
    let body={
        "media_id":mediaId
    }
    console.log('------------',mediaId)
    return that.getAcc().then(data=>{
        let access_token=data.access_token  //上面传来已经JSON.parse
        let url=`${api.permanentMediaId}access_token=${access_token}`;
        return that.getBody({url:url,method:'POST',body:body,json:true})
    })
};
Wechat.prototype.getPermanentCount=function(mediaId){  //获取所有素材总数量
    let that=this
    return that.getAcc().then(data=>{
        let access_token=data.access_token
        let url=`${api.getCount}access_token=${access_token}`;
        return that.getBody({url:url,method:'GET',json:true})
    })
};
Wechat.prototype.getList=function(postBody){  //所有指定类型（例news）素材的列表
    let that=this
    return that.getAcc().then(data=>{
        let access_token=data.access_token
        let url=`${api.getList}access_token=${access_token}`;
        return that.getBody({url:url,method:'POST',body:postBody,json:true})
    })
};
Wechat.prototype.delMedia=function(mediaId){  //删除素材
    let that=this
    form={
        "media_id":mediaId
    }
    return that.getAcc().then(data=>{
        let access_token=data.access_token
        let url=`${api.delMedia}access_token=${access_token}`;
        return that.getBody({url:url,method:'POST',body:form,json:true})
    })
};
//报错：errcode: 45009, errmsg: 'reach max api 某些接口调用次数超出，只能明天再试

//这里用公主号设定的token+字典排序，验证传来的signature票据是否一致。初次绑定域名用
exports.gJs=async (ctx,opts)=>{
    var we=new Wechat(opts)
    //下面都是微信要求提供的参数，token是在config中取，其他的均由提交域名时验证，并发送过来给我们获取
    let token=opts.token;
    let signature=ctx.request.query.signature;
    let nonce=ctx.request.query.nonce;
    let timestamp=ctx.request.query.timestamp;
    let echostr=ctx.request.query.echostr;
    //上面都是必须的参数，下面序列号数组并将数组去分隔符组成一长字符串
    let str=[token,timestamp,nonce].sort().join('');
    let sha=sha1(str);   //用sha1工具解密
    if(ctx.request.method==='GET'){
        if (sha===signature) {  //解密后与接收的signature一致则access通过，类似七牛上传步骤
            ctx.body=echostr+'';   //必须res响应出去（res是写，req是读/请求），给微信接口确认
            console.log('sha_access ok');
        }else{
            console.log('sha_err');
        }
    }else if(ctx.request.method==='POST'){
        return sha === signature
    }
};
exports.Wechat=Wechat;

